type OperationFunc = (a:number, b:number) => number;
type OperationSymbol = '+' | '-' | '*' | '÷';
type OperationObj = {
  func: OperationFunc;
  symb: OperationSymbol;
}

const add     : OperationFunc = (a, b) => (a + b)
const minus   : OperationFunc = (a, b) => (a >= b) ? (a - b) : (b - a)
const multiply: OperationFunc = (a, b) => (a * b)
const div     : OperationFunc = (a, b) => {
  const l = (a >= b) ? a : b;
  const s = (a >= b) ? b : a;
  if ( (l % s) !== 0) return -1
  else return l / s;
}

const operations: OperationObj[] = [
  { symb: '+', func: add },
  { symb: '-', func: minus },
  { symb: '*', func: multiply },
  { symb: '÷', func: div },
]

type StepTrace = {
  depth: number;
  input: number[];
  a?: number;
  b?: number;
  op?: OperationSymbol;
}

type Solution = StepTrace[];

const TARGET = 24;
type RecursiveFunc = (
  values: number[],
  depth:number,
  monitor: Solution,
  solutions: Solution[],
) => number | null;
export const roundTrial:RecursiveFunc = (values, depth, monitor, solutions) => {
  monitor[depth] = { depth, input: values }

  const len = values.length ?? 0
  if (len >= 2) {
    const lm1 = len - 1

    for (let i=0;i<lm1;i++) {
      const fidx = values.indexOf(values[i])
      if (fidx >= 0 && fidx < i) continue;

      monitor[depth]['a'] = values[i]
      for (let j=i+1;j<len;j++) {
        const fjdx = values.indexOf(values[j])
        if (fjdx >= 0 && fjdx < i) continue;

        monitor[depth]['b'] = values[j]
        for (const op of operations) {
          monitor[depth]['op'] = op.symb
          const newvalue = op.func(values[i], values[j])
  
          if (len > 2) {
            if (newvalue >= 0) {
              const next = [...values, newvalue]
              next.splice(j, 1)   // !!! j is more than i, so, remove index(j) first
              next.splice(i, 1)
              const result = roundTrial(next, depth + 1, monitor, solutions)
              if (result === TARGET) {
                if (depth > 0) {  // this will go through all posible solutions
                  return result
                }
              }
            }
          } else {
            if (newvalue === TARGET) {
              const solution:Solution = []
              monitor.forEach(element => {
                solution.push({...element})
              })
              solutions.push(solution)
             
              return newvalue
            }
          }
        }
      }
    }
  }

  return (solutions?.length || null)
}

const opDict = operations.reduce((accu:any, item:OperationObj) => {
  accu[item.symb] = item.func
  return accu
}, {})

export const format = (a:number, b:number, op: OperationSymbol) => {
  const l = (a >= b) ? a : b;
  const s = (a >= b) ? b : a;
  return `${l} ${op} ${s} = ${opDict[op](l, s)}`
}
